#include "alsa/asoundlib.h"
#include "log.h" 
#include "i2cps.h"
#include "uio.h"
#include "audio_adau1761.h" 
#include <memory.h>
#include <stdint.h>
#include <stdlib.h>   
#include <pthread.h>
#include <signal.h> 
#include <time.h> 

struct adau_ctx
{ 
	uint32_t* opt_buf; 
	uint32_t opt_buf_idx;
	uint32_t opt_buf_size;  
};

adau_ctx ctx;
pthread_t adau_thread;
snd_pcm_t *pcm; 
volatile uint8_t running;

extern "C" void int_handler(int dummy) { 
	loge("\ninterrupted!\n");
	running = 0; 
} 

static timespec ts;
uint64_t getns() {
	clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
	uint64_t ret = ts.tv_sec;
	ret *= 1000000000;
	ret += ts.tv_nsec;
	return ret;
}

void* adau_sampling(void* pctx)
{
	adau_ctx* ctx = (adau_ctx*)pctx;
	void* uio_ptr = setUIO(0, 65536);
	if (uio_ptr < 0) {
		loge("unable to mmap uio\n");
		exit(-1);
	}
	int iic_fd = setI2C(1, IIC_SLAVE_ADDR);
	if (iic_fd < 0) {
		loge("unable to set i2c %d\n", 1);
		exit(-1);
	}

	uint32_t status, l, r; 
	
	uint64_t start = getns();
	uint64_t sample_cnt = 1;
	double sample_rate = 48000.0;
	double second_ns = 1000000000.0;
	double next_sample_time = second_ns / sample_rate;

	while (running)
	{ 
		do {
			status =  *((volatile unsigned*)(((uint8_t*)uio_ptr) + I2S_STATUS_REG));
		} while (status == 0); 
		*((volatile unsigned*)(((uint8_t*)uio_ptr) + I2S_STATUS_REG)) = 0x00000001; 
		l = *((volatile int*)(((uint8_t*)uio_ptr) + I2S_DATA_RX_L_REG));
		r = *((volatile int*)(((uint8_t*)uio_ptr) + I2S_DATA_RX_R_REG)); 

		uint64_t t = getns() - start; 
		while(t < next_sample_time)
		{
			t = getns() - start;
		}

		ctx->opt_buf[ctx->opt_buf_idx++] = l;
		ctx->opt_buf[ctx->opt_buf_idx++] = r;
		if (ctx->opt_buf_idx >= ctx->opt_buf_size)
		{ 
			ctx->opt_buf_idx = 0; 
		}
		next_sample_time = second_ns * ++sample_cnt / sample_rate; 
	}

	if (unsetUIO(uio_ptr, 65536) < 0) {
		loge("unable to free UIO %d\n", 0);
	}
	if (unsetI2C(iic_fd) < 0) {
		loge("unable to unset I2C %d\n", 1);
	}
}   

int main(int argc, char ** argv)
{
	char * pcm_name = (char *)"default";
	if(argc >= 2)
	{
		pcm_name = argv[1];
		logd("pcm_name = %s\n", pcm_name);
	}

	int ret = snd_pcm_open(&pcm, pcm_name, SND_PCM_STREAM_PLAYBACK, 0);
	if(ret < 0){
		loge("audio open error %d\n", ret);
		return -1;
	}

	logd("initialize\n");
	running = 1;
	signal(SIGINT, int_handler);
 
	int latency = 10000;
	if(argc >= 3)
		sscanf(argv[2], "%d", &latency);
	ret = snd_pcm_set_params(pcm, SND_PCM_FORMAT_S24_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 2, 48000, 1, latency);
	if(ret < 0){
		loge("cannot set params\n"); 
		return -1;
	} 
	
	snd_pcm_uframes_t framesize, periodsize;
	snd_pcm_get_params(pcm, &framesize, &periodsize);
	logd("frame size = %d period size = %d\n", framesize, periodsize);
	
	uint32_t blocks = 16;
	ctx.opt_buf_size = periodsize * 2 * blocks; 
	ctx.opt_buf = new uint32_t[ctx.opt_buf_size];  
	ctx.opt_buf_idx = 0; 
	   
	config_audio_pll(1);
	config_audio_codec(1);
	select_line_in(1);

	ret = pthread_create(&adau_thread, NULL, adau_sampling, &ctx);
	if (ret) {
		loge("create adau thread failed");
		exit(-1);
	}   
 
	logd("adau priority max\n");
	sched_param param; 
	pthread_getschedparam(adau_thread, &ret, &param);
	param.sched_priority = sched_get_priority_max(ret);
	pthread_setschedparam(adau_thread, ret, &param);  

	uint32_t block_idx = 0;
	uint32_t block_cnt = blocks / 4; 

	uint64_t start_time = getns();
	double sample_rate = 48000.0;
	double block_time_numerator = periodsize * 1000000000 ; //need divide by sample_rate
	double next_feed_time = block_time_numerator * block_cnt / sample_rate; 

	snd_pcm_prepare(pcm);

	while (running)
	{
		uint64_t t = getns() - start_time;
		if(t >= next_feed_time)
		{ 
			uint32_t * buf = ctx.opt_buf + periodsize * 2 * block_idx++;
			if(block_idx == blocks) block_idx = 0;

			ret = snd_pcm_writei(pcm, buf, periodsize);
			if(ret == -EPIPE){
				snd_pcm_recover(pcm, ret, 0);
			}
			else if(ret < 0){ 
				loge("writei err %s\n", snd_strerror(ret));
			}
			else if(ret != periodsize){
				loge("short %d %d\n", ret, periodsize);
			}
			else {
				next_feed_time = block_time_numerator * (++block_cnt) / sample_rate; 
			}
		} 
	}
}